﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Runtime.Caching;

namespace IRC.IRepairClouds.DataLayer.Cache
{
    public abstract class StaticCacheManager
    {
        public static Tuple<string, Func<StaticCacheManager>>[] StaticCacheManagers = new Tuple<string, Func<StaticCacheManager>>[] {      
            Tuple.Create<string, Func<StaticCacheManager>>("User", () => CacheManager.Users),
        };

        public abstract void Flush();

        public static void FlushAll()
        {
            foreach (var i in StaticCacheManagers) {
                var scm = i.Item2();
                scm.Flush();
            }
        }

        public abstract IEnumerable<object> AllRaw { get; }
    }

    public abstract class StaticCacheManager<TC> : StaticCacheManager where TC : class
    {
        private class KeyComparer : IEqualityComparer<object[]>
        {
            public static readonly KeyComparer Default = new KeyComparer();

            private static class JenkinsOneAtATime
            {
                public static int ComputeHash(object[] data)
                {
                    unchecked {
                        uint hash = 0;
                        foreach (object o in data) {
                            int h = 0;
                            if (o != null)
                                h = o.GetHashCode();
                            hash += (uint)h;
                            hash += (hash << 10);	// 10
                            hash ^= (hash >> 6);	// 6
                        }
                        hash += (hash << 3);	// 3
                        hash ^= (hash >> 11);	// 11
                        hash += (hash << 15);	// 15
                        return (int)hash;
                    }
                }
            }

            bool IEqualityComparer<object[]>.Equals(object[] x, object[] y)
            {
                if (x == null && y == null)
                    return true;
                if (x == null || y == null)
                    return false;
                if (x.Length != y.Length)
                    return false;
                for (int i = 0; i < x.Length; i++) {
                    var o1 = x[i];
                    var o2 = y[i];
                    if (!o1.Equals(o2))
                        return false;
                }
                return true;
            }

            int IEqualityComparer<object[]>.GetHashCode(object[] obj)
            {
                return JenkinsOneAtATime.ComputeHash(obj);
            }
        }
        protected object sync = new object();
        private bool _initialLoaded = false;

        private MultiKeyDictionary<object[], TC> cache = new MultiKeyDictionary<object[], TC>(KeyComparer.Default);

        protected abstract IEnumerable<object[]> GetCachedKeys(TC obj);

        protected virtual void InitialLoad()
        {
        }

        private void TriggerInitialLoad()
        {
            if (!_initialLoaded) {
                lock (sync) {
                    if (!_initialLoaded) {
                        InitialLoad();
                        _initialLoaded = true;
                    }
                }
            }
        }

        private void InsertIntoCache(TC data, Func<TC, IEnumerable<object[]>> getCachedKeys)
        {
            if (data == null)
                return;
            var keys = getCachedKeys(data).ToArray();
            cache.Set(data, keys);
        }

        protected TC GetBase(params object[] keys)
        {
            TriggerInitialLoad();
            TC data;
            if (cache.TryGetValue(keys, out data))
                return (TC)data;
            //ExceptionHandling.Log("Object not found in static cache [" + this.GetType().Name + "], keys= " + keys.JSON_BaseSerializeEx(), Webix.Diagnostics.Logging.LogSeverity.Warning);
            return null;
        }

        protected void InsertInitialLoadBase(IEnumerable<TC> datas)
        {
            lock (sync) {
                foreach (var data in datas)
                    InsertIntoCache(data, this.GetCachedKeys);
            }
        }

        public override IEnumerable<object> AllRaw
        {
            get { return cache.Values.Cast<object>(); }
        }

        public IEnumerable<TC> All
        {
            get
            {
                TriggerInitialLoad();
                return cache.Values;
            }
        }

        public override void Flush()
        {
            lock (sync) {
                cache.Clear();
                _initialLoaded = false;
            }
        }
    }
}
